﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NetFrameworkExtensions.Mathematics
{
  public struct IntRational : IEquatable<IntRational>, IComparable<IntRational>, ICloneable
  {
    public static IntRational PositiveInfinity = new IntRational(1, 0);
    public static IntRational NegativeInfinity = new IntRational(-1, 0);
    public static IntRational NaN = new IntRational(0, 0);

    int numerator;
    int denominator;

    public IntRational(int numerator, int denominator = 1)
    {
      this.numerator = numerator;
      this.denominator = denominator;
      Normalize();
    }

    public int Numerator { get { return numerator; } }
    public int Denominator { get { return denominator; } }

    public bool IsNaN { get { return denominator == 0 && numerator == 0; } }
    public bool IsPositiveInfinity { get { return denominator == 0 && numerator == 1; } }
    public bool IsNegativeInfinity { get { return denominator == 0 && numerator == -1; } }
    public bool IsInfinity { get { return denominator == 0 && numerator != 0; } }
    public bool IsFinite { get { return denominator != 0; } }
    public bool IsZero { get { return denominator != 0 && numerator == 0; } }
    public bool IsInteger { get { return denominator == 1; } }
    public bool IsNegative { get { return numerator < 0; } }
    public bool IsPositive { get { return numerator > 0; } }

    public IntRational Add(IntRational other)
    {
      if ((denominator == 0 || other.denominator == 0) && this == other) return this;
      return new IntRational(numerator * other.denominator + other.numerator * denominator, denominator * other.denominator);
    }
    public IntRational Substract(IntRational other)
    {
      return this.Add(other.Opposite());
    }
    public IntRational Opposite()
    {
      return new IntRational(-numerator, denominator);
    }
    public IntRational Inverse()
    {
      return new IntRational(denominator, numerator);
    }
    public IntRational Multiply(IntRational other)
    {
      return new IntRational(numerator * other.numerator, denominator * other.denominator);
    }
    public IntRational Divide(IntRational other)
    {
      if (IsInfinity && other.IsFinite && other.IsNegative)
        return this.Opposite();
      return new IntRational(numerator * other.denominator, denominator * other.numerator);
    }

    public static IntRational operator +(IntRational r1, IntRational r2)
    {
      return r1.Add(r2);
    }
    public static IntRational operator -(IntRational r1, IntRational r2)
    {
      return r1.Substract(r2);
    }
    public static IntRational operator -(IntRational r)
    {
      return r.Opposite();
    }
    public static IntRational operator *(IntRational r1, IntRational r2)
    {
      return r1.Multiply(r2);
    }
    public static IntRational operator /(IntRational r1, IntRational r2)
    {
      return r1.Divide(r2);
    }
    
    public static implicit operator IntRational(int i)
    {
      return new IntRational(i);
    }
    public static explicit operator int(IntRational r)
    {
      return r.ToInt();
    }
    public static explicit operator double(IntRational r)
    {
      return r.ToDouble();
    }
    public static explicit operator float(IntRational r)
    {
      return r.ToSingle();
    }
    public static explicit operator decimal(IntRational r)
    {
      return r.ToDecimal();
    }

    public override string ToString()
    {
      if (denominator == 0)
      {
        if (this == PositiveInfinity) return "+Inf";
        if (this == NegativeInfinity) return "-Inf";
        return "NaN";
      }
      if (denominator == 1)
        return numerator.ToString();
      return string.Format("{0} / {1}", numerator, denominator);
    }
    public int ToInt()
    {
      if (denominator == 0) throw new NotFiniteNumberException(ToDouble());
      return numerator / denominator;
    }
    public double ToDouble()
    {
      if (denominator == 0)
      {
        switch (numerator)
        {
          case -1: return double.PositiveInfinity;
          case 0: return double.NaN;
          case 1: return double.NegativeInfinity;
        }
      }
      return (double)numerator / (double)denominator;
    }
    public float ToSingle()
    {
      if (denominator == 0)
      {
        switch (numerator)
        {
          case -1: return float.PositiveInfinity;
          case 0: return float.NaN;
          case 1: return float.NegativeInfinity;
        }
      }
      return (float)numerator / (float)denominator;
    }
    public decimal ToDecimal()
    {
      if (denominator == 0) throw new NotFiniteNumberException(ToDouble());
      return (decimal)numerator / (decimal)denominator;
    }

    #region IEquatable<IntRational> Members

    public bool Equals(IntRational other)
    {
      return numerator == other.numerator && denominator == other.denominator;
    }

    public override bool Equals(object obj)
    {
      return obj is IntRational && Equals((IntRational)obj);
    }

    public override int GetHashCode()
    {
      unchecked
      {
        return numerator * 368 + denominator;
      }
    }

    public static bool operator==(IntRational r1, IntRational r2)
    {
      return r1.Equals(r2);
    }

    public static bool operator !=(IntRational r1, IntRational r2)
    {
      return !r1.Equals(r2);
    }

    #endregion

    #region IComparable<IntRational> Members

    public int CompareTo(IntRational other)
    {
      int comp = numerator * other.denominator - other.numerator * denominator;
      return comp;
    }

    public static bool operator <(IntRational r1, IntRational r2)
    {
      return r1.CompareTo(r2) < 0;
    }

    public static bool operator >(IntRational r1, IntRational r2)
    {
      return r1.CompareTo(r2) > 0;
    }

    public static bool operator <=(IntRational r1, IntRational r2)
    {
      return r1.CompareTo(r2) <= 0;
    }

    public static bool operator >=(IntRational r1, IntRational r2)
    {
      return r1.CompareTo(r2) >= 0;
    }
    #endregion

    #region ICloneable Members

    public object Clone()
    {
      return this;
    }

    #endregion

    private void Normalize()
    {
      if (denominator == 0)
      {
        numerator = Math.Sign(numerator);
        return;
      }
      if (denominator < 0)
      {
        denominator = -denominator;
        numerator = -numerator;
      }
      int gcd = numerator.GCD(denominator);
      denominator /= gcd;
      numerator /= gcd;
    }
  }
}
